﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using CalculatorLogic;



namespace CalculatorGUI
{
    public partial class GraphControl : UserControl
    {        
        CalculatorLogic.GraphGenerator graphGenerator;
        CalculatorLogic.GraphSetting graphSetting;
        CalculatorLogic.EquationParser equationParser;
        CalculatorLogic.PostfixSolver solver;
        //CalculatorLogic.Algebraic_Math.Solve algebraSolver;
        //CalculatorLogic.Equation algebraEquation;
        CalculatorLogic.GraphSolutionFinder graphSolutionFinder;

        EqxControl eqxControl;
        MainPage mp;

        List<CalcTextBox> eqxList = new List<CalcTextBox>();

        Point xMinPoint;
        Point xMaxPoint;
        Point yMinPoint;
        Point yMaxPoint;

        double canvasWidth;
        double canvasHeight;
        double canvasBorder = 5;

        double xScale;
        double yScale;
        double xOffset;
        double yOffset;
        double yInterval = 1;
        double xInterval = 1;

        double tickLength = .25;

        double labelFontSize = 18;

        double xTrace;
        Equation equation = new Equation();

        GraphSetting dragStartPoint;

        /// <summary>
        /// Constructor
        /// </summary>
        public GraphControl()
        {
            InitializeComponent();

            canvasWidth = graphCanvas.ActualWidth - 2 * canvasBorder;
            canvasHeight = graphCanvas.ActualHeight - 2 * canvasBorder;

            //This recatangle is what the canvas uses for clipping, anything that is drawn outside the borders of the canvas,
            //are cut off, otherwise some of the steeper lines and curves were stretching across the entire page

            RectangleGeometry rect = new RectangleGeometry();
            rect.Rect = new Rect(0, 0, graphCanvas.ActualWidth, graphCanvas.ActualHeight);
            graphCanvas.Clip = rect;

            //instantiate this GraphGenerator thingy
            graphGenerator = CalculatorLogic.GraphGenerator.GetGraphGenerator();
            //then the graph setting
            graphSetting = new CalculatorLogic.GraphSetting(-10d, 10d, 1d, -10d, 10d, 1d);

            equationParser = new CalculatorLogic.EquationParser();
            solver = new CalculatorLogic.PostfixSolver();
            //algebraSolver = new CalculatorLogic.Algebraic_Math.Solve();
            //algebraEquation = new CalculatorLogic.Equation();

            graphSolutionFinder = new CalculatorLogic.GraphSolutionFinder();
            
            //draw the axes right away so they are always on the canvas
            drawTicks();

            drawLabels();

            drawAxes();
        }

        /// <summary>
        /// Getter and setter for a reference to the mainpage
        /// </summary>
        public MainPage Mp
        {
            get { return mp; }
            set { mp = value; }
        }

        /// <summary>
        /// method to update the graph after changes have been made to axes or equations
        /// </summary>
        public void UpdateGraph()
        {
            graphCanvas.Children.Clear();

            //The axes must be drawn first because the method also handles the graphsettings for the ticks, labels and equations
            drawAxes();

            drawTicks();

            drawLabels();

            drawEqx();
        }

        /// <summary>
        /// This one is kind of obvious, draws the axes
        /// </summary>
        private void drawAxes()
        {
            CalculatorLogic.Graph graph = graphGenerator.GenerateGraph(graphSetting.XMin, graphSetting.XMax, graphSetting.XScale, graphSetting.YMin, graphSetting.YMax, graphSetting.YScale);

            xMinPoint = graph.XAxis.StartPoint;
            xMaxPoint = graph.XAxis.EndPoint;
            yMinPoint = graph.YAxis.StartPoint;
            yMaxPoint = graph.YAxis.EndPoint;
            xInterval = graphSetting.XScale;
            yInterval = graphSetting.YScale;

            //We need to scale the drawing to fit the canvas minus the border
            xScale = canvasWidth / (xMaxPoint.X - xMinPoint.X);
            yScale = canvasHeight / (yMaxPoint.Y - yMinPoint.Y);

            //then we offset for 0 and for the white space we want to leave around the border
            xOffset = canvasBorder;
            if (xMinPoint.X < 0)
	        {
		        xOffset += xScale * (0 - xMinPoint.X);
	        }

            yOffset = canvasBorder;
            if (yMaxPoint.Y > 0)
            {
                yOffset += yScale * (0 + yMaxPoint.Y);
            }

            //now we make our x axis
            Polyline xAxis = new Polyline();
            //sets the color, could be set programatically elsewhere
            xAxis.Stroke = new SolidColorBrush(Colors.Black);
            //sets the line thickness
            xAxis.StrokeThickness = 3;
            //adding our points to a new collection
            PointCollection xAxisPoints = new PointCollection();
            xAxisPoints.Add(xMaxPoint);
            xAxisPoints.Add(xMinPoint);
            //then we adjust the points for scale and offset right before putting them into the polyline
            xAxis.Points = adjustPoints(xAxisPoints);

            //now we do it all again for y
            Polyline yAxis = new Polyline();
            yAxis.Stroke = new SolidColorBrush(Colors.Black);

            yAxis.StrokeThickness = 3;
            PointCollection yAxisPoints = new PointCollection();
            
            yAxisPoints.Add(yMinPoint);
            yAxisPoints.Add(yMaxPoint);
            yAxis.Points = adjustPoints(yAxisPoints);

            //make sure to add the polylines to the canvas
            graphCanvas.Children.Add(xAxis);
            graphCanvas.Children.Add(yAxis);
        }

        /// <summary>
        /// Loops through each of the equation textboxes to draw the equations
        /// </summary>
        private void drawEqx()
        {

            eqxControl = Mp.EqxControl1;
            //Clear the list of equation labels
            eqxList.Clear();
            //Clear the stack panel of equation label objects
            EqxStackPanel.Children.Clear();

            for (int i = 0; i < eqxControl.TxtList.Count; i++)
            {
                if (eqxControl.ChkList[i].IsChecked == true && eqxControl.TxtList[i].Text.Length > 0)
                {
                    //Call out to the logic to get the points to plot our graph
                    PointCollection eq1Points = new PointCollection();
                    eq1Points = graphSolutionFinder.FindSolutionPoints(eqxControl.TxtList[i].Text, graphSetting);
                    Polyline EQ1 = new Polyline();
                    EQ1.Stroke = eqxControl.TxtList[i].Foreground;
                    EQ1.StrokeThickness = 3;
                    EQ1.Points = adjustPoints(eq1Points);
                    //Add it to the canvas
                    graphCanvas.Children.Add(EQ1);

                    //Make a textbox that shows the equation on the graph page
                    CalcTextBox ctb = new CalcTextBox();
                    ctb.Text = " y = " + eqxControl.TxtList[i].Text;
                    ctb.ForeGround = eqxControl.TxtList[i].Foreground;
                    ctb.Margin = new Thickness(10, 10, 0, 0);
                    ctb.Width = 200;
                    ctb.MouseLeftButtonDown += new MouseButtonEventHandler (ctb_MouseLeftButtonDown);
                    ctb.Tag = i;
                    //Add it to the equation panel
                    EqxStackPanel.Children.Add(ctb);
                    //Add the text control to a list for use with the trace function
                    eqxList.Add(ctb);
                }
            }
        }

        /// <summary>
        /// draws the interval markers on the axes
        /// </summary>
        private void drawTicks()
        { 
            //have to handle each x and y positive and negative or markers may be off
            if (xMinPoint.X < 0 && yMinPoint.Y <= 0 && yMaxPoint.Y >= 0)
            {
                for (double i = 0; i >= xMinPoint.X; i -= xInterval)
                {
                    if (i < xMaxPoint.X)
                    {
                        PointCollection p = new PointCollection();
                        Polyline l = new Polyline();
                        l.Stroke = new SolidColorBrush(Colors.Black);
                        l.StrokeThickness = 2;
                        p.Add(new Point(i, 0));
                        p.Add(new Point(i, tickLength));
                        l.Points = adjustPoints(p);
                        //add it to the canvas
                        graphCanvas.Children.Add(l);
                    }
                }
            }
            if (xMaxPoint.X > 0 && yMinPoint.Y <= 0 && yMaxPoint.Y >= 0)
            {
                for (double i = 0; i <= xMaxPoint.X; i += xInterval)
                {
                    if (i > xMinPoint.X)
                    {
                        PointCollection p = new PointCollection();
                        Polyline l = new Polyline();
                        l.Stroke = new SolidColorBrush(Colors.Black);
                        l.StrokeThickness = 2;
                        p.Add(new Point(i, 0));
                        p.Add(new Point(i, tickLength));
                        l.Points = adjustPoints(p);
                        graphCanvas.Children.Add(l);
                    }
                }
            }
            if (yMinPoint.Y < 0 && xMinPoint.X <= 0 && xMaxPoint.X >= 0)
            {
                for (double i = 0; i >= yMinPoint.Y; i -= yInterval)
                {
                    if (i < yMaxPoint.Y)
                    {
                        PointCollection p = new PointCollection();
                        Polyline l = new Polyline();
                        l.Stroke = new SolidColorBrush(Colors.Black);
                        l.StrokeThickness = 2;
                        p.Add(new Point(0, i));
                        p.Add(new Point(tickLength, i));
                        l.Points = adjustPoints(p);
                        graphCanvas.Children.Add(l);
                    }
                }
            }
            if (yMaxPoint.Y > 0 && xMinPoint.X <= 0 && xMaxPoint.X >= 0)
            {
                for (double i = 0; i <= yMaxPoint.Y; i += yInterval)
                {
                    if (i > yMinPoint.Y)
                    {
                        PointCollection p = new PointCollection();
                        Polyline l = new Polyline();
                        l.Stroke = new SolidColorBrush(Colors.Black);
                        l.StrokeThickness = 2;
                        p.Add(new Point(0, i));
                        p.Add(new Point(tickLength, i));
                        l.Points = adjustPoints(p);
                        graphCanvas.Children.Add(l);
                    }
                }
            }
        }

        /// <summary>
        /// Draws the labels on the screen
        /// </summary>
        private void drawLabels()
        {
            //this brush will be used for all labels
            Brush labelBrush = new SolidColorBrush(Colors.Black);
            if (yMinPoint.Y <= 0 && yMaxPoint.Y >= 0)
            {
                TextBlock xMinLabel = new TextBlock();
                xMinLabel.Text = "( " + (Convert.ToDecimal(xMinPoint.X)).ToString("F") + " , " + (Convert.ToDecimal(xMinPoint.Y)).ToString("F") + " )";
                xMinLabel.FontSize = labelFontSize;
                xMinLabel.Foreground = labelBrush;
                xMinLabel.SetValue(Canvas.LeftProperty, canvasBorder);
                xMinLabel.SetValue(Canvas.TopProperty, yOffset + 5);
                //add it to the canvas
                graphCanvas.Children.Add(xMinLabel);
            }
            if (yMinPoint.Y <= 0 && yMaxPoint.Y >= 0)
            {
                TextBlock xMaxLabel = new TextBlock();
                xMaxLabel.Text = "( " + (Convert.ToDecimal(xMaxPoint.X)).ToString("F") + " , " + (Convert.ToDecimal(xMaxPoint.Y)).ToString("F") + " )";
                xMaxLabel.FontSize = labelFontSize;
                xMaxLabel.Foreground = labelBrush;
                xMaxLabel.SetValue(Canvas.LeftProperty, graphCanvas.Width - xMaxLabel.ActualWidth - canvasBorder);
                xMaxLabel.SetValue(Canvas.TopProperty, yOffset + 5);
                //add it to the canvas
                graphCanvas.Children.Add(xMaxLabel);
            }
            if (xMinPoint.X <= 0 && xMaxPoint.X >= 0)
            {
                TextBlock yMinLabel = new TextBlock();
                yMinLabel.Text = "( " + (Convert.ToDecimal(yMinPoint.X)).ToString("F") + " , " + (Convert.ToDecimal(yMinPoint.Y)).ToString("F") + " )";
                yMinLabel.FontSize = labelFontSize;
                yMinLabel.Foreground = labelBrush;
                yMinLabel.SetValue(Canvas.LeftProperty, xOffset + 5);
                yMinLabel.SetValue(Canvas.TopProperty, graphCanvas.Height - yMinLabel.ActualHeight - canvasBorder);
                //add it to the canvas
                graphCanvas.Children.Add(yMinLabel);
            }
            if (xMinPoint.X <= 0 && xMaxPoint.X >= 0)
            {
                TextBlock yMaxLabel = new TextBlock();
                yMaxLabel.Text = "( " + (Convert.ToDecimal(yMaxPoint.X)).ToString("F") + " , " + (Convert.ToDecimal(yMaxPoint.Y)).ToString("F") + " )";
                yMaxLabel.FontSize = labelFontSize;
                yMaxLabel.Foreground = labelBrush;
                yMaxLabel.SetValue(Canvas.LeftProperty, xOffset + 5);
                yMaxLabel.SetValue(Canvas.TopProperty, canvasBorder);
                //add it to the canvas
                graphCanvas.Children.Add(yMaxLabel);
            }
        }

        /// <summary>
        /// This one adjusts each point in a collection so that it is drawn in the right place on the canvas
        /// </summary>
        /// <param name="P"></param>
        /// <returns></returns>
        private PointCollection adjustPoints(PointCollection P)
        {
            for (int i = 0; i < P.Count; i++)
            {
                Point ptemp = P[i];
                //offsetting and scaling the x and y
                ptemp.X = P[i].X * xScale + xOffset;
                ptemp.Y = (0 - P[i].Y) * yScale + yOffset;
                P[i] = ptemp;
            }
            return P;
        }

        /// <summary>
        /// Handles clicks on the equation text boxes for the trace feature
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ctb_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (((CalcTextBox)sender).Selected == true)
            {
                clearTrace();
                for (int i = 0; i < eqxList.Count; i++)
                {
                    eqxList[i].Selected = false;
                }
            }
            else
            {
                for (int i = 0; i < eqxList.Count; i++)
                {
                    if (eqxList[i].Selected)
                    {
                        clearTrace();
                    }
                    eqxList[i].Selected = false;
                }
                ((CalcTextBox)sender).Selected = true;
                xTrace = 0;
                trace(xTrace);
            }
        }

        /// <summary>
        /// Draws the trace line and label
        /// </summary>
        /// <param name="x"></param>
        private void trace(double j)
        {
            //here we have to handle if a value is passed in is out of bounds
            if (j < xMinPoint.X)
            {
                j = xMinPoint.X;
                xTrace = xMinPoint.X;
            }
            if (j > xMaxPoint.X)
            {
                j = xMaxPoint.X;
                xTrace = xMaxPoint.X;
            }
            string s;
            double temp;
            PointCollection tracePoints = new PointCollection();
            //We loop through the equations to find the one that is selected
            for (int i = 0; i < eqxList.Count; i++)
            {
                if (eqxList[i].Selected)
                {
                    //and then we draw the trace line
                    s = eqxControl.TxtList[Convert.ToInt32(eqxList[i].Tag)].Text;
                    s = s.Replace("x", j.ToString());
                    s = equationParser.Parse(s);
                    temp = Convert.ToDouble(solver.Solve(s,equation));
                    tracePoints.Add(new Point(j, temp));
                    tracePoints.Add(new Point(j, 0));
                    Polyline t = new Polyline();
                    t.Stroke = new SolidColorBrush(Colors.Purple);
                    t.StrokeThickness = 2;
                    t.Points = adjustPoints(tracePoints);
                    //add it to the canvas
                    graphCanvas.Children.Add(t);

                    //and then we label the trace line near the x axis
                    TextBlock traceLabel = new TextBlock();
                    //for the labels we have to convert to decimal first so we can truncate when converting to string
                    //or else the labels could stretch all the way across the screen  because of precision
                    traceLabel.Text = "( " + Convert.ToDecimal(j).ToString("F") + " , " + Convert.ToDecimal(temp).ToString("F") + " )";
                    traceLabel.FontSize = labelFontSize;
                    traceLabel.Foreground = new SolidColorBrush(Colors.Purple);
                    traceLabel.SetValue(Canvas.LeftProperty, j * xScale + xOffset);
                    traceLabel.SetValue(Canvas.TopProperty, yOffset - traceLabel.ActualHeight - .25 * xScale);
                    //add it to the canvas
                    graphCanvas.Children.Add(traceLabel);
                }
            }
        }

        /// <summary>
        /// Clears the trace line and label from the canvas
        /// </summary>
        private void clearTrace()
        {
            for (int j = 0; j < eqxList.Count; j++)
            {
                if (eqxList[j].Selected)
                {
                    for (int i = graphCanvas.Children.Count - 1; i >= 0; i--)
                    {
                        if (graphCanvas.Children[i] is Polyline)
                        {
                            //removing the trace line from the canvas
                            graphCanvas.Children.RemoveAt(i);
                            break;
                        }
                    }
                    for (int i = graphCanvas.Children.Count - 1; i >= 0; i--)
                    {
                        if (graphCanvas.Children[i] is TextBlock)
                        {
                            //removing the trace label from the canvas
                            graphCanvas.Children.RemoveAt(i);
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// handles the clicks on the trace button arrows
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tracebutton_Click(object sender, RoutedEventArgs e)
        {
            xTrace = xTrace + Convert.ToInt32(((Button)sender).Tag) * (xMaxPoint.X - xMinPoint.X) / 240;
            clearTrace();
            trace(xTrace);
        }

        /// <summary>
        /// Handles the clicks on the zoom buttons
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void zoomButton_Click(object sender, RoutedEventArgs e)
        {
            //the buttons are actually going to pass in the percentage to zoom in or out
            double xWidth = (xMaxPoint.X - xMinPoint.X);
            double yHeight = (yMaxPoint.Y - yMinPoint.Y);

            graphSetting.XMax = graphSetting.XMax + Convert.ToDouble(((Button)sender).Tag) * xWidth / 2;
            graphSetting.XMin = graphSetting.XMin - Convert.ToDouble(((Button)sender).Tag) * xWidth / 2;
            graphSetting.YMax = graphSetting.YMax + Convert.ToDouble(((Button)sender).Tag) * yHeight / 2;
            graphSetting.YMin = graphSetting.YMin - Convert.ToDouble(((Button)sender).Tag) * yHeight / 2;

            UpdateGraph();
        }

        /// <summary>
        /// Scrolls the graph in the y direction
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void yScrollButton_Click(object sender, RoutedEventArgs e)
        {
            double yScroll = (yMaxPoint.Y - yMinPoint.Y) / 5;

            graphSetting.YMin = graphSetting.YMin + Convert.ToDouble(((Button)sender).Tag) * yScroll;
            graphSetting.YMax = graphSetting.YMax + Convert.ToDouble(((Button)sender).Tag) * yScroll;
            UpdateGraph();
        }

        /// <summary>
        /// Scrolls the graph in the x direction
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void xScrollButton_Click(object sender, RoutedEventArgs e)
        {
            double xScroll = (xMaxPoint.X - xMinPoint.X) / 5;

            graphSetting.XMin = graphSetting.XMin + Convert.ToDouble(((Button)sender).Tag) * xScroll;
            graphSetting.XMax = graphSetting.XMax + Convert.ToDouble(((Button)sender).Tag) * xScroll;
            UpdateGraph();
        }

        private void ZeroButton_Click(object sender, RoutedEventArgs e)
        {
            graphSetting = new CalculatorLogic.GraphSetting(-10d, 10d, 1d, -10d, 10d, 1d);            
            UpdateGraph();
        }


        double initialScale;


        private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
        {
            initialScale = graphSetting.XScale;
        }
        private void OnPinchDelta(object sender, PinchGestureEventArgs e)
        {

        }
        private void OnPinchCompleted(object sender, PinchGestureEventArgs e)
        {
            graphSetting.XScale = initialScale * e.DistanceRatio;
        }

        private void OnDragStarted(object sender, DragStartedGestureEventArgs e)
        {
            dragStartPoint = graphSetting;

        }

        private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
        {

        }

        private void OnDragCompleted(object sender, DragCompletedGestureEventArgs e)
        {
            graphSetting = new GraphSetting(Convert.ToDouble(dragStartPoint.XMin - (e.HorizontalChange / 18)),
                                            Convert.ToDouble(dragStartPoint.XMax - (e.HorizontalChange / 18)),
                                            Convert.ToDouble(1),
                                            Convert.ToDouble(dragStartPoint.YMin + (e.VerticalChange / 18)),
                                            Convert.ToDouble(dragStartPoint.YMax + (e.VerticalChange / 18)),
                                            Convert.ToDouble(1));
            UpdateGraph();
        }
    }
}
